<?php

class Pop3Component extends Object {

/*
	class.POP3.php3 v1.0	99/03/24 CDI cdi@thewebmasters.net
	Copyright (c) 1999 - CDI (cdi@thewebmasters.net) All Rights Reserved

	An RFC 1939 compliant wrapper class for the POP3 protocol.

	Cakified by Scott Fennell
*/


	var $ERROR		= "";		//	Error string.
	var $TIMEOUT	= 60;		//	Default timeout before giving up on a
								//	network operation.
	var $COUNT		= -1;		//	Mailbox msg count
	var $BUFFER		= 512;		//	Socket buffer for socket fgets() calls.
								//	Per RFC 1939 the returned line a POP3
								//	server can send is 512 bytes.

	var $FP			= "";		//	The connection to the server's
								//	file descriptor

	var $MAILSERVER	= "";		// Set this to hard code the server name

	var $DEBUG		= false;	// set to true to echo pop3
								// commands and responses to error_log
								// this WILL log passwords!

	var $BANNER		= "";		//	Holds the banner returned by the
								//	pop server - used for apop()

	var $RFC1939	= true;		//	Set by noop(). See rfc1939.txt	
								//

	var $ALLOWAPOP	= false;	//	Allow or disallow apop()
								//	This must be set to true
								//	manually.

	function POP3 ( $server = "", $timeout = "" )
	{
		settype($this->BUFFER,"integer");
		if(!empty($server))
		{
			// Do not allow programs to alter MAILSERVER
			// if it is already specified. They can get around
			// this if they -really- want to, so don't count on it.
			if(empty($this->MAILSERVER))
			{
				$this->MAILSERVER = $server;
			}
		}
		if(!empty($timeout))
		{
			settype($timeout,"integer");
			$this->TIMEOUT = $timeout;
			set_time_limit($timeout);
		}
		return true;
	}

	function update_timer ()
	{
		set_time_limit($this->TIMEOUT);
		return true;
	}

	function connect ($server, $port = 110)
	{
		//	Opens a socket to the specified server. Unless overridden,
		//	port defaults to 110. Returns true on success, false on fail

		// If MAILSERVER is set, override $server with it's value

		if(!empty($this->MAILSERVER))
		{
			$server = $this->MAILSERVER;
		}

		if(empty($server))
		{
			$this->ERROR = "POP3 connect: No server specified";  
			unset($this->FP);
			return false;
		}

		$fp = fsockopen("$server", $port, &$errno, &$errstr);

		if(!$fp)
		{
			$this->ERROR = "POP3 connect: Error [$errno] [$errstr]";
			unset($this->FP);
			return false;
		}

		set_socket_blocking($fp,-1);
		$this->update_timer();
		$reply = fgets($fp,$this->BUFFER);
		$reply = $this->strip_clf($reply);
		if($this->DEBUG) { error_log("POP3 SEND [connect: $server] GOT [$reply]",0); }
		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 connect: Error [$reply]";
			unset($this->FP);
			return false;
		}
		$this->FP = $fp;
		$this->BANNER = $this->parse_banner($reply);
		$this->RFC1939 = $this->noop();
		if($this->RFC1939)
		{
			$this->ERROR = "POP3: premature NOOP OK, NOT an RFC 1939 Compliant server";
			$this->quit();
			return false;
		}
		return true;
	}

	function noop ()
	{
		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 noop: No connection to server";
			return false;
		}
		$cmd = "NOOP";
		$reply = $this->send_cmd($cmd);
		if(!$this->is_ok($reply))
		{
			return false;
		}
		return true;
	}

	function user ($user = "")
	{
		// Sends the USER command, returns true or false

		if(empty($user))
		{
			$this->ERROR = "POP3 user: no user id submitted";
			return false;
		}
		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 user: connection not established";
			return false;
		}
		$reply = $this->send_cmd("USER $user");
		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 user: Error [$reply]";
			return false;
		}
		return true;
	}

	function pass ($pass = "")
	{
		// Sends the PASS command, returns # of msgs in mailbox,
		// returns false (undef) on Auth failure

		if(empty($pass))
		{
			$this->ERROR = "POP3 pass: no password submitted";
			return false;
		}
		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 pass: connection not established";
			return false;
		}
		$reply = $this->send_cmd("PASS $pass");
		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 pass: authentication failed [$reply]";
			$this->quit();
			return false;
		}
		//	Auth successful.
		$count = $this->last("count");
		$this->COUNT = $count;
		$this->RFC1939 = $this->noop();
		if(!$this->RFC1939)
		{
			$this->ERROR = "POP3 pass: NOOP failed. Server not RFC 1939 compliant";
			$this->quit();
			return false;
		}
		return $count;
	}

	function apop ($login,$pass)
	{
		//	Attempts an APOP login. If this fails, it'll
		//	try a standard login. YOUR SERVER MUST SUPPORT
		//	THE USE OF THE APOP COMMAND!
		//	(apop is optional per rfc1939)

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 apop: No connection to server";
			return false;
		}

		if(!$this->ALLOWAPOP)
		{
			$retVal = $this->login($login,$pass);
			return $retVal;
		}

		if(empty($login))
		{
			$this->ERROR = "POP3 apop: No login ID submitted";
			return false;
		}
		if(empty($pass))
		{
			$this->ERROR = "POP3 apop: No password submitted";
			return false;
		}
		$banner = $this->BANNER;
		if( (!$banner) or (empty($banner)) )
		{
			$this->ERROR = "POP3 apop: No server banner - aborted";
			$retVal = $this->login($login,$pass);
			return $retVal;
		}
		$AuthString = $banner;
		$AuthString .= $pass;
		$APOPString = md5($AuthString);
		$cmd = "APOP $login $APOPString";
		$reply = $this->send_cmd($cmd);
		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 apop: apop authentication failed - abort";
			$retVal = $this->login($login,$pass);
			return $retVal;
		}
		//	Auth successful.
		$count = $this->last("count");
		$this->COUNT = $count;
		$this->RFC1939 = $this->noop();
		if(!$this->RFC1939)
		{
			$this->ERROR = "POP3 apop: NOOP failed. Server not RFC 1939 compliant";
			$this->quit();
			return false;
		}
		return $count;
	}

	function login ($login = "", $pass = "")
	{
		// Sends both user and pass. Returns # of msgs in mailbox or
		// false on failure (or -1, if the error occurs while getting
		// the number of messages.)

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 login: No connection to server";
			return false;
		}
		$fp = $this->FP;
		if(!$this->user($login))
		{
			//	Preserve the error generated by user()
			return false;
		}
		$count = $this->pass($pass);
		if( (!$count) or ($count == -1) )
		{
			//	Preserve the error generated by last() and pass()
			return false;
		}
		return $count;
	}

	function top ($msgNum, $numLines = "0")
	{
		//	Gets the header and first $numLines of the msg body
		//	returns data in an array with each returned line being
		//	an array element. If $numLines is empty, returns
		//	only the header information, and none of the body.

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 top: No connection to server";
			return false;
		}
		$this->update_timer();

		$fp = $this->FP;
		$buffer = $this->BUFFER;
		$cmd = "TOP $msgNum $numLines";
		fwrite($fp, "TOP $msgNum $numLines\r\n");
		$reply = fgets($fp, $buffer);
		$reply = $this->strip_clf($reply);
		if($this->DEBUG) { @error_log("POP3 SEND [$cmd] GOT [$reply]",0); }
		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 top: Error [$reply]";
			return false;
		}

		$count = 0;
		$MsgArray = array();

		$line = fgets($fp,$buffer);
		while ( !ereg("^\.\r\n",$line))
		{
			$MsgArray[$count] = $line;
			$count++;
			$line = fgets($fp,$buffer);
			if(empty($line))	{ break; }
		}

		return $MsgArray;
	}

	function pop_list ($msgNum = "")
	{
		//	If called with an argument, returns that msgs' size in octets
		//	No argument returns an associative array of undeleted 
		//	msg numbers and their sizes in octets

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 pop_list: No connection to server";
			return false;
		}
		$fp = $this->FP;
		$Total = $this->COUNT;
		if( (!$Total) or ($Total == -1) )
		{
			return false;
		}
		if($Total == 0)
		{
			return array("0","0");
			// return -1;	// mailbox empty
		}

		$this->update_timer();

		if(!empty($msgNum))
		{
			$cmd = "LIST $msgNum";
			fwrite($fp,"$cmd\r\n");
			$reply = fgets($fp,$this->BUFFER);
			$reply = $this->strip_clf($reply);
			if($this->DEBUG) { @error_log("POP3 SEND [$cmd] GOT [$reply]",0); }
			if(!$this->is_ok($reply))
			{
				$this->ERROR = "POP3 pop_list: Error [$reply]";
				return false;
			}
			list($junk,$num,$size) = explode(" ",$reply);
			return $size;
		}
		$cmd = "LIST";
		$reply = $this->send_cmd($cmd);
		if(!$this->is_ok($reply))
		{
			$reply = $this->strip_clf($reply);
			$this->ERROR = "POP3 pop_list: Error [$reply]";
			return false;
		}
		$MsgArray = array();
		$MsgArray[0] = $Total;
		for($msgC=1;$msgC <= $Total; $msgC++)
		{
			if($msgC > $Total) { break; }
			$line = fgets($fp,$this->BUFFER);
			$line = $this->strip_clf($line);
			if(ereg("^\.",$line))
			{
				$this->ERROR = "POP3 pop_list: Premature end of list";
				return false;
			}
			list($thisMsg,$msgSize) = explode(" ",$line);
			settype($thisMsg,"integer");
			if($thisMsg != $msgC)
			{
				$MsgArray[$msgC] = "deleted";
			}
			else
			{
				$MsgArray[$msgC] = $msgSize;
			}
		}
		return $MsgArray;
	}

	function get ($msgNum)
	{
		//	Retrieve the specified msg number. Returns an array
		//	where each line of the msg is an array element.

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 get: No connection to server";
			return false;
		}

		$this->update_timer();

		$fp = $this->FP;
		$buffer = $this->BUFFER;
		$cmd = "RETR $msgNum";
		$reply = $this->send_cmd($cmd);

		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 get: Error [$reply]";
			return false;
		}

		$count = 0;
		$MsgArray = array();

		$line = fgets($fp,$buffer);
		while ( !ereg("^\.\r\n",$line))
		{
			$MsgArray[$count] = $line;
			$count++;
			$line = fgets($fp,$buffer);
			if(empty($line))	{ break; }
		}
		return $MsgArray;
	}

	function last ( $type = "count" )
	{
		//	Returns the highest msg number in the mailbox.
		//	returns -1 on error, 0+ on success, if type != count
		//	results in a popstat() call (2 element array returned)

		$last = -1;
		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 last: No connection to server";
			return $last;
		}

		$reply = $this->send_cmd("STAT");
		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 last: error [$reply]";
			return $last;
		}

		$Vars = explode(" ",$reply);
		$count = $Vars[1];
		$size = $Vars[2];
		settype($count,"integer");
		settype($size,"integer");
		if($type != "count")
		{
			return array($count,$size);
		}
		return $count;
	}

	function reset ()
	{
		//	Resets the status of the remote server. This includes
		//	resetting the status of ALL msgs to not be deleted.
		//	This method automatically closes the connection to the server.

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 reset: No connection to server";
			return false;
		}
		$reply = $this->send_cmd("RSET");
		if(!$this->is_ok($reply))
		{
			//	The POP3 RSET command -never- gives a -ERR
			//	response - if it ever does, something truely
			//	wild is going on.

			$this->ERROR = "POP3 reset: Error [$reply]";
			@error_log("POP3 reset: ERROR [$reply]",0);
		}
		$this->quit();
		return true;
	}

	function send_cmd ( $cmd = "" )
	{
		//	Sends a user defined command string to the
		//	POP server and returns the results. Useful for
		//	non-compliant or custom POP servers.
		//	Do NOT include the \r\n as part of your command
		//	string - it will be appended automatically.

		//	The return value is a standard fgets() call, which
		//	will read up to $this->BUFFER bytes of data, until it
		//	encounters a new line, or EOF, whichever happens first.

		//	This method works best if $cmd responds with only
		//	one line of data.

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 send_cmd: No connection to server";
			return false;
		}

		if(empty($cmd))
		{
			$this->ERROR = "POP3 send_cmd: Empty command string";
			return "";
		}

		$fp = $this->FP;
		$buffer = $this->BUFFER;
		$this->update_timer();
		fwrite($fp,"$cmd\r\n");
		$reply = fgets($fp,$buffer);
		$reply = $this->strip_clf($reply);
		if($this->DEBUG) { @error_log("POP3 SEND [$cmd] GOT [$reply]",0); }
		return $reply;
	}

	function quit ()
	{
		//	Closes the connection to the POP3 server, deleting
		//	any msgs marked as deleted.

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 quit: connection does not exist";
			return false;
		}
		$fp = $this->FP;
		$cmd = "QUIT";
		fwrite($fp,"$cmd\r\n");
		$reply = fgets($fp,$this->BUFFER);
		$reply = $this->strip_clf($reply);
		if($this->DEBUG) { @error_log("POP3 SEND [$cmd] GOT [$reply]",0); }
		fclose($fp);
		unset($this->FP);
		return true;
	}

	function popstat ()
	{
		//	Returns an array of 2 elements. The number of undeleted
		//	msgs in the mailbox, and the size of the mbox in octets.

		$PopArray = $this->last("array");

		if($PopArray == -1) { return false; }

		if( (!$PopArray) or (empty($PopArray)) )
		{
			return false;
		}
		return $PopArray;
	}

	function uidl ($msgNum = "")
	{
		//	Returns the UIDL of the msg specified. If called with
		//	no arguments, returns an associative array where each
		//	undeleted msg num is a key, and the msg's uidl is the element
		//	Array element 0 will contain the total number of msgs

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 uidl: No connection to server";
			return false;
		}

		$fp = $this->FP;
		$buffer = $this->BUFFER;

		if(!empty($msgNum))
		{
			$cmd = "UIDL $msgNum";
			$reply = $this->send_cmd($cmd);
			if(!$this->is_ok($reply))
			{
				$this->ERROR = "POP3 uidl: Error [$reply]";
				return false;
			}
			list ($ok,$num,$myUidl) = explode(" ",$reply);
			return $myUidl;
		}
		else
		{
			$this->update_timer();

			$UIDLArray = array();
			$Total = $this->COUNT;
			$UIDLArray[0] = $Total;

			if ($Total < 1)
			{
				return $UIDLArray;
			}
			$cmd = "UIDL";
			fwrite($fp, "UIDL\r\n");
			$reply = fgets($fp, $buffer);
			$reply = $this->strip_clf($reply);
			if($this->DEBUG) { @error_log("POP3 SEND [$cmd] GOT [$reply]",0); }
			if(!$this->is_ok($reply))
			{
				$this->ERROR = "POP3 uidl: Error [$reply]";
				return false;
			}

			$line = "";
			$count = 1;
			$line = fgets($fp,$buffer);
			while ( !ereg("^\.\r\n",$line))
			{
				if(ereg("^\.\r\n",$line))
				{
					break;
				}
				list ($msg,$msgUidl) = explode(" ",$line);
				$msgUidl = $this->strip_clf($msgUidl);
				if($count == $msg)
				{
					$UIDLArray[$msg] = $msgUidl;
				}
				else
				{
					$UIDLArray[$count] = "deleted";
				}
				$count++;
				$line = fgets($fp,$buffer);
			}
		}
		return $UIDLArray;
	}

	function delete ($msgNum = "")
	{
		//	Flags a specified msg as deleted. The msg will not
		//	be deleted until a quit() method is called.

		if(!isset($this->FP))
		{
			$this->ERROR = "POP3 delete: No connection to server";
			return false;
		}
		if(empty($msgNum))
		{
			$this->ERROR = "POP3 delete: No msg number submitted";
			return false;
		}
		$reply = $this->send_cmd("DELE $msgNum");
		if(!$this->is_ok($reply))
		{
			$this->ERROR = "POP3 delete: Command failed [$reply]";
			return false;
		}
		return true;
	}

	//	*********************************************************

	//	The following methods are internal to the class.

	function is_ok ($cmd = "")
	{
		//	Return true or false on +OK or -ERR

		if(empty($cmd))					{ return false; }
		if ( ereg ("^\+OK", $cmd ) )	{ return true; }
		return false;
	}

	function strip_clf ($text = "")
	{
		// Strips \r\n from server responses

		if(empty($text)) { return $text; }
		$stripped = ereg_replace("\r","",$text);
		$stripped = ereg_replace("\n","",$stripped);
		return $stripped;
	}

	function parse_banner ( $server_text )
	{
		$outside = true;
		$banner = "";
		$length = strlen($server_text);
		for($count =0; $count < $length; $count++)
		{
			$digit = substr($server_text,$count,1);
			if(!empty($digit))
			{
				if( (!$outside) and ($digit != '<') and ($digit != '>') )
				{
					$banner .= $digit;
				}
				if ($digit == '<')
				{
					$outside = false;
				}
				if($digit == '>')
				{
					$outside = true;
				}
			}
		}
		$banner = $this->strip_clf($banner);	// Just in case
		return "<$banner>";
	}

}


?>